/* Copyright (C) 2014  Jean-Sebastien Castonguay
 *
 * This file is part of TechnoBoard.
 *
 * TechnoBoard is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * TechnoBoard is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with TechnoBoard.  If not, see <http://www.gnu.org/licenses/>. */

#include "console.h"
#include "technoboard.h"
#include "version.h"
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>


#define COMMAND_PROMPT   "cmd>"
#define FIELD_SEPARATOR  ","
#define END_OF_COMMAND   '\r'
#define END_OF_LINE      "\n\r"

#define CMD_HELP         "help"
#define CMD_VERSION      "version"
#define CMD_GET_ADC      "getadc"
#define CMD_CONF_GPIO    "confgpio"
#define CMD_SET_GPIO     "setgpio"
#define CMD_GET_GPIO     "getgpio"
#define CMD_START_PWM    "startpwm"
#define CMD_PWM_DC       "pwmdc"
#define CMD_STOP_PWM     "stoppwm"
#define CMD_START_DAC    "startdac"
#define CMD_SET_DAC      "setdac"
#define CMD_STOP_DAC     "stopdac"

#define ERROR_EXPECTED_PARAMETER     "Parameter expected"
#define ERROR_NO_PARAMETER_EXPECTED  "No parameter expected"
#define ERROR_WRONG_PARAMETER        "Wrong parameter"

#define COMMAND_BUFFER_SIZE  80

typedef struct {
    char buffer[COMMAND_BUFFER_SIZE];
    unsigned int Index;
} CommandBuffer;

static CommandBuffer commandBuffer = {
    .Index = 0
};

static Timer_ms ADCProcessPeriod = 0;


static bool AddChar(CommandBuffer * const buffer, char c) {

    bool charAdded;

    if (buffer->Index < COMMAND_BUFFER_SIZE) {
        buffer->buffer[buffer->Index] = c;
        buffer->Index++;
        charAdded = true;
    } else {
        charAdded = false;
    }

    return charAdded;
}


static void Empty(CommandBuffer * const buffer) {
    buffer->Index = 0;
}


static void SendError( char * const errorMessage) {
    USB_PrintString("Error: ");
    USB_PrintString(errorMessage);
}


static bool GetCommand(CommandBuffer * const buffer) {

    bool commandReceived = false;
    
    while (USB_IsDataReceived()) {

        char c = USB_GetChar();

        if (c == END_OF_COMMAND) {
            commandReceived = true;
            c = NULL;  // Used to make the command a NULL terminated string.
        }

        if (AddChar(buffer, c) == false) {
            SendError("Command line too long: 80 char max");
            USB_PrintString(COMMAND_PROMPT);
            Empty(buffer);
        }
    }

    return commandReceived;
}


static void ProcessVersionCommand() {
    USB_PrintString("tblib: ");
    USB_PrintString(TECHNOBOARD_LIB_VERSION END_OF_LINE);
    USB_PrintString("DAQ&C: ");
    USB_PrintString(APPLICATION_VERSION);
}


static void ProcessHelpCommand() {

    char * parameter = strtok(NULL, FIELD_SEPARATOR);

    if (parameter == NULL) {
        USB_PrintString(END_OF_LINE);
        USB_PrintString("technoboard application" END_OF_LINE);
        USB_PrintString("=======================" END_OF_LINE);
        ProcessVersionCommand();
        USB_PrintString("Here are the available commands:" END_OF_LINE);
        USB_PrintString("  help:       Display this information" END_OF_LINE);
        USB_PrintString("  version:    Display the technoboard lib and application versions");
    } else {
        SendError(ERROR_NO_PARAMETER_EXPECTED);
    }
}


static void PrintADCValues() {
    USB_PrintString("adc,");
    USB_PrintInt( ADC_GetRawValue(ADC_P2_3));
    USB_PrintString(FIELD_SEPARATOR);
    USB_PrintInt( ADC_GetRawValue(ADC_P2_4));
}


static void ADCProcess() {

    static Timer_Tick timeout = 0;

    if (ADCProcessPeriod > 0) {
        if (Timer_GetElapseTime(timeout) > ADCProcessPeriod) {
            timeout = Timer_GetTick();
            PrintADCValues();
            USB_PrintString(END_OF_LINE);
        }
    }
}


static void ProcessGetAdcCommand() {

    char * period = strtok(NULL, FIELD_SEPARATOR);

    if (period != NULL) {
        ADCProcessPeriod = atoi(period);
        if (ADCProcessPeriod > 0) {
            ADC_Start();
        } else {
            ADC_Stop();
        }
    } else {
        ADC_Start();
        while (ADC_IsDataValid() == false) {;}
        PrintADCValues();
        ADC_Stop();
    }
}


static void ProcessConfGPIOCommand() {

    char * gpio = strtok(NULL, FIELD_SEPARATOR);
    char * directionStr = strtok(NULL, FIELD_SEPARATOR);

    if ((gpio != NULL) && (directionStr != NULL)) {

        unsigned int dir = directionStr[0];
        GPIO_Direction direction;
        bool directionFound = true;

        if (directionStr[0] == 'i') {
            direction = GPIO_INPUT;
        } else if (directionStr[0] == 'o') {
            direction = GPIO_OUTPUT;
        } else {
            SendError(ERROR_WRONG_PARAMETER);
            directionFound = false;
        }

        if (directionFound) {
            if (strcmp(gpio, "3.1") == 0) {
                GPIO_P3_1_DIR = direction;
            } else if (strcmp(gpio, "3.2") == 0) {
                GPIO_P3_2_DIR = direction;
            } else if (strcmp(gpio, "3.3") == 0) {
                GPIO_P3_3_DIR = direction;
            } else if (strcmp(gpio, "3.4") == 0) {
                GPIO_P3_4_DIR = direction;
            } else if (strcmp(gpio, "4.1") == 0) {
                GPIO_P4_1_DIR = direction;
            } else if (strcmp(gpio, "4.2") == 0) {
                GPIO_P4_2_DIR = direction;
            } else if (strcmp(gpio, "4.3") == 0) {
                GPIO_P4_3_DIR = direction;
            } else if (strcmp(gpio, "4.4") == 0) {
                GPIO_P4_4_DIR = direction;
            } else if (strcmp(gpio, "4.7") == 0) {
                GPIO_P4_7_DIR = direction;
            } else if (strcmp(gpio, "4.8") == 0) {
                GPIO_P4_8_DIR = direction;
            } else if (strcmp(gpio, "4.9") == 0) {
                GPIO_P4_9_DIR = direction;
            } else if (strcmp(gpio, "4.10") == 0) {
                GPIO_P4_10_DIR = direction;
            } else {
                SendError(ERROR_WRONG_PARAMETER);
            }
        }
    } else {
        USB_PrintString(ERROR_EXPECTED_PARAMETER);
    }
}


static void ProcessSetGPIOCommand() {

    char * gpio = strtok(NULL, FIELD_SEPARATOR);
    char * levelStr = strtok(NULL, FIELD_SEPARATOR);

    if ((gpio != NULL) && (levelStr != NULL)) {

        unsigned int level = atoi(levelStr);
        
        if (strcmp(gpio, "3.1") == 0) {
            GPIO_P3_1_PIN = level;
        } else if (strcmp(gpio, "3.2") == 0) {
            GPIO_P3_2_PIN = level;
        } else if (strcmp(gpio, "3.3") == 0) {
            GPIO_P3_3_PIN = level;
        } else if (strcmp(gpio, "3.4") == 0) {
            GPIO_P3_4_PIN = level;
        } else if (strcmp(gpio, "4.1") == 0) {
            GPIO_P4_1_PIN = level;
        } else if (strcmp(gpio, "4.2") == 0) {
            GPIO_P4_2_PIN = level;
        } else if (strcmp(gpio, "4.3") == 0) {
            GPIO_P4_3_PIN = level;
        } else if (strcmp(gpio, "4.4") == 0) {
            GPIO_P4_4_PIN = level;
        } else if (strcmp(gpio, "4.7") == 0) {
            GPIO_P4_7_PIN = level;
        } else if (strcmp(gpio, "4.8") == 0) {
            GPIO_P4_8_PIN = level;
        } else if (strcmp(gpio, "4.9") == 0) {
            GPIO_P4_9_PIN = level;
        } else if (strcmp(gpio, "4.10") == 0) {
            GPIO_P4_10_PIN = level;
        } else {
            SendError(ERROR_WRONG_PARAMETER);
        }

    } else {
        USB_PrintString(ERROR_EXPECTED_PARAMETER);
    }
}


static void ProcessGetGPIOCommand() {

    char * gpio = strtok(NULL, FIELD_SEPARATOR);

    if (gpio != NULL) {

        bool level;
        bool pinFound = true;

        if (strcmp(gpio, "3.1") == 0) {
            level = GPIO_P3_1_PIN;
        } else if (strcmp(gpio, "3.2") == 0) {
            level = GPIO_P3_2_PIN;
        } else if (strcmp(gpio, "3.3") == 0) {
            level = GPIO_P3_3_PIN;
        } else if (strcmp(gpio, "3.4") == 0) {
            level = GPIO_P3_4_PIN;
        } else if (strcmp(gpio, "4.1") == 0) {
            level = GPIO_P4_1_PIN;
        } else if (strcmp(gpio, "4.2") == 0) {
            level = GPIO_P4_2_PIN;
        } else if (strcmp(gpio, "4.3") == 0) {
            level = GPIO_P4_3_PIN;
        } else if (strcmp(gpio, "4.4") == 0) {
            level = GPIO_P4_4_PIN;
        } else if (strcmp(gpio, "4.7") == 0) {
            level = GPIO_P4_7_PIN;
        } else if (strcmp(gpio, "4.8") == 0) {
            level = GPIO_P4_8_PIN;
        } else if (strcmp(gpio, "4.9") == 0) {
            level = GPIO_P4_9_PIN;
        } else if (strcmp(gpio, "4.10") == 0) {
            level = GPIO_P4_10_PIN;
        } else {
            SendError(ERROR_WRONG_PARAMETER);
            pinFound = false;
        }

        if (pinFound) {
            USB_PrintString("gpio,");
            USB_PrintInt(level);
        }

    } else {
        USB_PrintString(ERROR_EXPECTED_PARAMETER);
    }
}


static void ProcessStartPWMCommand() {
    
    char * outputStr = strtok(NULL, FIELD_SEPARATOR);
    char * sourcePeriodStr = strtok(NULL, FIELD_SEPARATOR);
    char * periodStr = strtok(NULL, FIELD_SEPARATOR);
    char * dutyCycleStr = strtok(NULL, FIELD_SEPARATOR);

    if ((outputStr != NULL) && (sourcePeriodStr != NULL) && (periodStr != NULL) && (dutyCycleStr != NULL)) {

        PWM_Output output = atoi(outputStr);
        PWM_TimerSourcePeriod timerScrPeriod = atoi(sourcePeriodStr);
        unsigned int pwmPeriod = atoi(periodStr);
        unsigned int dutyCycle = atoi(dutyCycleStr);

        if (PWM_Start( output, timerScrPeriod, pwmPeriod, dutyCycle) == false) {
            SendError(ERROR_WRONG_PARAMETER);
        }
    } else {
        SendError(ERROR_EXPECTED_PARAMETER);
    }
}


static void ProcessPWMDCCommand() {

    char * outputStr = strtok(NULL, FIELD_SEPARATOR);
    char * dutyCycleStr = strtok(NULL, FIELD_SEPARATOR);

    if ((outputStr != NULL) && (dutyCycleStr != NULL)) {

        PWM_Output output = atoi(outputStr);
        unsigned int dutyCycle = atoi(dutyCycleStr);

        if (PWM_SetDutyCycle( output, dutyCycle) == false) {
            SendError(ERROR_WRONG_PARAMETER);
        }
    } else {
        SendError(ERROR_EXPECTED_PARAMETER);
    }
}


static void ProcessStopPWMCommand() {

    char * outputStr = strtok(NULL, FIELD_SEPARATOR);

    if (outputStr != NULL) {

        PWM_Output output = atoi(outputStr);

        if (PWM_Stop( output) == false) {
            SendError(ERROR_WRONG_PARAMETER);
        }
    } else {
        SendError(ERROR_EXPECTED_PARAMETER);
    }
}


static void ProcessStartDACCommand() {

    char * parameter = strtok(NULL, FIELD_SEPARATOR);

    if (parameter == NULL) {
        DAC_Start();
    } else {
        SendError(ERROR_NO_PARAMETER_EXPECTED);
    }
}


static void ProcessSetDACCommand() {
    
    char * outputStr = strtok(NULL, FIELD_SEPARATOR);
    char * rawValueStr = strtok(NULL, FIELD_SEPARATOR);

    if ((outputStr != NULL) && (rawValueStr != NULL)) {

        DAC_Output output = atoi(outputStr);
        unsigned int rawValue = atoi(rawValueStr);

        if (DAC_SetRawValue( output, rawValue) == false) {
            SendError(ERROR_WRONG_PARAMETER);
        }
    } else {
        SendError(ERROR_EXPECTED_PARAMETER);
    }
}


static void ProcessStopDACCommand() {

    char * parameter = strtok(NULL, FIELD_SEPARATOR);

    if (parameter == NULL) {
        DAC_Stop();
    } else {
        SendError(ERROR_NO_PARAMETER_EXPECTED);
    }
}


void Console_CyclicProcess() {

    if (GetCommand(&commandBuffer)) {
        if (commandBuffer.Index > 1) {

            char * commandName = strtok(commandBuffer.buffer, FIELD_SEPARATOR);

            if (strcmp( commandName, CMD_HELP) == 0) {
                ProcessHelpCommand();
            } else if (strcmp( commandName, CMD_VERSION) == 0) {
                ProcessVersionCommand();
            } else if (strcmp( commandName, CMD_GET_ADC) == 0) {
                ProcessGetAdcCommand();
            } else if (strcmp( commandName, CMD_CONF_GPIO) == 0) {
                ProcessConfGPIOCommand();
            } else if (strcmp( commandName, CMD_SET_GPIO) == 0) {
                ProcessSetGPIOCommand();
            } else if (strcmp( commandName, CMD_GET_GPIO) == 0) {
                ProcessGetGPIOCommand();
            } else if (strcmp( commandName, CMD_START_PWM) == 0) {
                ProcessStartPWMCommand();
            } else if (strcmp( commandName, CMD_PWM_DC) == 0) {
                ProcessPWMDCCommand();
            } else if (strcmp( commandName, CMD_STOP_PWM) == 0) {
                ProcessStopPWMCommand();
            } else if (strcmp( commandName, CMD_START_DAC) == 0) {
                ProcessStartDACCommand();
            } else if (strcmp( commandName, CMD_SET_DAC) == 0) {
                ProcessSetDACCommand();
            } else if (strcmp( commandName, CMD_STOP_DAC) == 0) {
                ProcessStopDACCommand();
            } else {
                SendError("Unknown command");
            }
        }
        
        Empty(&commandBuffer);
        USB_PrintString(END_OF_LINE);
        USB_PrintString(COMMAND_PROMPT);
    }

    ADCProcess();
}
